#include "my_str.h"

static char *my_sprintf_num(char *buf, char *last, uint64_t ui64,
    char zero, uint64_t hexadecimal, uint64_t width);

int
my_snprintf(char *buf, size_t max, const char *fmt, ...)
{
    char        *p;
    va_list     args;

    va_start(args, fmt);
    p = my_vslprintf(buf, buf + max, fmt, args);
    va_end(args);

    return (int)(p - buf);
}

int
my_slprintf(char *buf, char *last, const char *fmt, ...)
{
    char        *p;
    va_list     args;

    va_start(args, fmt);
    p = my_vslprintf(buf, last, fmt, args);
    va_end(args);

    return (int)(p - buf);
}

char *
my_vslprintf(char *buf, char *last, const char *fmt, va_list args)
{
    if (buf >= last) {
        return buf;
    }

    int64_t                 i64;
    uint64_t                u64;
    my_str_t                *v;
    char                    *p, zero;
    int                     d;
    double                  f;
    
    int64_t                 unsign;
    uint64_t                width, frac_width, hex, n, frac, scale;
    size_t                  len;

    zero = '0';

    while (*fmt && buf < last) {
        if (*fmt == '%') {
            i64 = 0;
            u64 = 0;
            hex = 0;
            unsign = 0;
            width = 0;
            frac_width = 8; // 8 decimal places

            fmt++;
            while (*fmt >= '0' && *fmt <= '9') {
                width = width * 10 + (uint64_t)(*fmt++ - '0');
            }

            for ( ;; ) {
                switch(*fmt) {
                case 'l':
                    fmt++;
                    continue;

                case 'u':
                    unsign = 1;
                    fmt++;
                    continue;

                case 'x':
                    hex = 1;
                    unsign = 0;
                    fmt++;
                    continue;

                case 'X':
                    hex = 2;
                    unsign = 0;
                    fmt++;
                    continue;

                case '.':
                    fmt++;
                    frac_width = 0;
                    while (*fmt >= '0' && *fmt <= '9') {
                        frac_width = frac_width * 10 + (uint64_t)(*fmt++ - '0');
                    }

                    break;
                
                default:
                    break;
                }
                
                break;
            }

            switch (*fmt) {
            case 'v':
                v = va_arg(args, my_str_t *);
                len = MY_MIN((size_t)(last - buf), (size_t)v->len);
                buf = MY_CPYMEM(buf, v->data, len);
                fmt++;

                continue;

            case 's':
                p = va_arg(args, char *);
                len = MY_MIN((size_t)(last - buf), strlen(p));
                buf = MY_CPYMEM(buf, p, len);
                fmt++;

                continue;

            case 'c':
                d = (int)va_arg(args, int);
                *buf++ = (char) (d & 0xff);
                fmt++;
                continue;

            case 'd':
                if (unsign) {
                    u64 = (uint64_t) va_arg(args, uint64_t);
                } else {
                    i64 = (int64_t) va_arg(args, int64_t);
                }

                break;

            case 'p':
                u64 = (uintptr_t) va_arg(args, void *);
                hex = 2;
                unsign = 1;

                if (buf < last) {
                    *buf++ = '0';
                }

                if (buf < last) {
                    *buf++ = 'x';
                }

                width = sizeof(void *);
                break;

            case 'f':
                f = va_arg(args, double);
                if (f < 0) {
                    *buf++ = '-';
                    f = -f;
                }

                u64 = (uint64_t) f;
                frac = 0;
                if (frac_width) {
                    scale = 1;
                    for (n = frac_width; n; n--) {
                        scale *= 10;
                    }

                    frac = (uint64_t) ((f - (double) u64) * (double) scale + 0.5);
                    if (frac == scale) {
                        u64++;
                        frac = 0;
                    }
                }

                buf = my_sprintf_num(buf, last, u64, zero, 0, width);
                if (frac_width) {
                    if (buf < last) {
                        *buf++ = '.';
                    }

                    buf = my_sprintf_num(buf, last, frac, '0', 0, frac_width);
                }

                fmt++;
                continue;

            default:
                *buf++ = *fmt++;
                continue;
            }

            if (!unsign) {
                if (i64 < 0) {
                    *buf++ = '-';
                    u64 = (uint64_t) -i64;
                } else {
                    u64 = (uint64_t) i64;
                }
            }

            buf = my_sprintf_num(buf, last, u64, zero, hex, width);
            fmt++;
        } else {
            *buf++ = *fmt++;
        }
    }

    return buf;
}

static char *
my_sprintf_num(char *buf, char *last, uint64_t ui64, char zero, 
    uint64_t hexadecimal, uint64_t width)
{
    u_char         *p, temp[MY_INT64_LEN + 1];
                       /*
                        * we need temp[NGX_INT64_LEN] only,
                        * but icc issues the warning
                        */
    size_t          len;
    uint32_t        ui32;
    static u_char   hex[] = "0123456789abcdef";
    static u_char   HEX[] = "0123456789ABCDEF";

    p = temp + MY_INT64_LEN;

    if (hexadecimal == 0) {

        if (ui64 <= (uint64_t) UINT32_MAX) {

            /*
             * To divide 64-bit numbers and to find remainders
             * on the x86 platform gcc and icc call the libc functions
             * [u]divdi3() and [u]moddi3(), they call another function
             * in its turn.  On FreeBSD it is the qdivrem() function,
             * its source code is about 170 lines of the code.
             * The glibc counterpart is about 150 lines of the code.
             *
             * For 32-bit numbers and some divisors gcc and icc use
             * a inlined multiplication and shifts.  For example,
             * unsigned "i32 / 10" is compiled to
             *
             *     (i32 * 0xCCCCCCCD) >> 35
             */

            ui32 = (uint32_t) ui64;

            do {
                *--p = (u_char) (ui32 % 10 + '0');
            } while (ui32 /= 10);

        } else {
            do {
                *--p = (u_char) (ui64 % 10 + '0');
            } while (ui64 /= 10);
        }

    } else if (hexadecimal == 1) {

        do {

            /* the "(uint32_t)" cast disables the BCC's warning */
            *--p = hex[(uint32_t) (ui64 & 0xf)];

        } while (ui64 >>= 4);

    } else { /* hexadecimal == 2 */

        do {

            /* the "(uint32_t)" cast disables the BCC's warning */
            *--p = HEX[(uint32_t) (ui64 & 0xf)];

        } while (ui64 >>= 4);
    }

    /* zero or space padding */

    len = (size_t) ((temp + MY_INT64_LEN) - p);

    while (len++ < width && buf < last) {
        *buf++ = zero;
    }

    /* number safe copy */

    len = (size_t) ((temp + MY_INT64_LEN) - p);

    if (buf + len > last) {
        len = (size_t) (last - buf);
    }

    return MY_CPYMEM(buf, p, len);
}